﻿using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Quickly.Spa.Shared;
using System.Text;
using System.Text.Encodings.Web;

namespace Quickly.SpaProxy;

internal sealed class SpaProxyMiddleware
{
    public RequestDelegate _next { get; }
    public SpaProxyLaunchManager _spaProxyLaunchManager { get; }
    public IOptions<SpaDevelopmentServerOptions> _options { get; }
    public ILogger<SpaProxyMiddleware> _logger { get; }
    private readonly IHostApplicationLifetime _hostLifetime;

    public SpaProxyMiddleware(
        RequestDelegate next,
        SpaProxyLaunchManager spaProxyLaunchManager,
        IOptions<SpaDevelopmentServerOptions> options,
        IHostApplicationLifetime hostLifetime,
        ILogger<SpaProxyMiddleware> logger)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _spaProxyLaunchManager = spaProxyLaunchManager ?? throw new ArgumentNullException(nameof(spaProxyLaunchManager));
        _options = options ?? throw new ArgumentNullException(nameof(options));
        _hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public Task Invoke(HttpContext context)
    {
        if (MatchSpaServerUrl(context.Request.Path))
        {
            return InvokeCore(context);
        }

        return _next(context);
    }

    private async Task InvokeCore(HttpContext context)
    {
        context.Response.Headers[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate, max-age=0";

        var (name, option) = GetMatchSpaDevelopmentServerOption(context.Request.Path);

        if (option == null || string.IsNullOrWhiteSpace(name))
        {
            await _next(context);
            return;
        }

        var redirectUrl = option.GetRedirectUrl();

        if (!await _spaProxyLaunchManager.IsSpaProxyRunning(context.RequestAborted, name))
        {
            _spaProxyLaunchManager.StartInBackground(_hostLifetime.ApplicationStopping, name);
            _logger.LogInformation("SPA proxy is not ready. Returning temporary landing page.");
            context.Response.ContentType = "text/html";

            await using var writer = new StreamWriter(context.Response.Body, Encoding.UTF8);
            await writer.WriteAsync(GenerateSpaLaunchPage(option));
        }
        else
        {
            if (redirectUrl.StartsWith('/'))
            {
                await _next(context);
                return;
            }

            if (_logger.IsEnabled(LogLevel.Information))
            {
                _logger.LogInformation($"SPA proxy is ready. Redirecting to {redirectUrl}.");
            }

            context.Response.Redirect(redirectUrl);
        }
    }

    private (string? name, SpaDevelopmentServerOption? option) GetMatchSpaDevelopmentServerOption(PathString path)
    {
        foreach (var server in _options.Value)
        {
            if (MatchRequestPath(path, server.Value))
            {
                return (server.Key, server.Value);
            }
        }

        return (null, null);
    }

    private string? GetRedirectUrl(PathString path)
    {
        var (_, option) = GetMatchSpaDevelopmentServerOption(path);

        return option?.GetRedirectUrl();
    }

    static string GenerateSpaLaunchPage(SpaDevelopmentServerOption option)
    {
        return $@"
<!DOCTYPE html>
<html lang=""en"">
<head>
  <meta charset = ""UTF-8"" >
  <meta http-equiv=""X-UA-Compatible"" content=""IE=edge"">
  <meta http-equiv=""refresh"" content=""3"">
  <meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
  <title>SPA proxy launch page</title>
</head>
<body>
  <h1>Launching the SPA proxy...</h1>
  <p>This page will automatically redirect to <a href=""{HtmlEncoder.Default.Encode(option.GetRedirectUrl())}"">{HtmlEncoder.Default.Encode(option.GetRedirectUrl())}</a> when the SPA proxy is ready.</p>
</body>
</html>";
    }

    /// <summary>
    /// 寻找匹配的Spa Development Server
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    private bool MatchSpaServerUrl(PathString path)
    {
        var (_, option) = GetMatchSpaDevelopmentServerOption(path);

        return option != null;
    }

    private static bool MatchRequestPath(PathString path, SpaDevelopmentServerOption option)
    {
        return path.Equals(new Uri(option.ServerUrl).LocalPath);
    }
}
